import MetaTrader5 as mt5
import os
import sys
import json
import numpy as np
import pandas as pd
import time
from datetime import datetime, timedelta
import random
from colorama import Fore, Style, init
from wcwidth import wcswidth
from sklearn.decomposition import PCA


SYMBOL = "XAUUSD"
CONTRACT_SIZE = 100
REFRESH_INTERVAL = 60
EXTREMEVAL=60
BARS = 60
TARGET_POSITION=1

PROFIT_RANGE=0.25
ALERT = {
    "leverage": 3.0,
    "inventory": 5.0,
    "rsi": 70,
    "atr": 50,
    "price_pos_up": 0.85,
    "price_pos_low": 0.15,
    "activity_high": 1.5,
    "activity_low": 0.5
}

TIMEFRAMES = {
    #"M1": mt5.TIMEFRAME_M1,
    #"M5": mt5.TIMEFRAME_M5,
    "M15": mt5.TIMEFRAME_M15,
   # "H1": mt5.TIMEFRAME_H1,
    # "H4": mt5.TIMEFRAME_H4,
    "D1": mt5.TIMEFRAME_D1,
    "W1": mt5.TIMEFRAME_W1
}
import finplot as fplt
import plotly.graph_objects as go

class XFactorLab:
    def __init__(self, window=20, vol_threshold=2.0):
        self.window = window
        self.vol_threshold = vol_threshold
        self.comm_factor={}
        self.volume=0

    def initialize(self):
        config =self.load_config()
        self.symbol=config["symbol"]
        self.lost=self.fmt_num(config["lost"])
        #self.symbol='XAGUSDz'
        if not mt5.initialize(login=int(config["account"]), server=config['server'],password=config['password']):
            print("登陆失败，错误提示：",mt5.last_error())
            os.system("pause")


        # symbol = "XAUUSD"
        # timeframe = mt5.TIMEFRAME_M15   # 15分钟
        # num_bars = 200                  # 获取最近 200 根 K 线

        # # -----------------------------
        # # 2. 从 MT5 获取数据
        # # -----------------------------
        # rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, num_bars)
       
        # # 转成 DataFrame
        # df = pd.DataFrame(rates)
        # df['time'] = pd.to_datetime(df['time'], unit='s')

        # # 保留常用列
        # df = df[['time','open','high','low','close','tick_volume']]

        # fplt.create_plot("XAUUSD 15min", rows=1)

        # # K 线
        # fplt.candlestick_ochl(
        #     df[['open', 'close', 'high', 'low']].values,
        #     df['time']
        # )

        # # 加 20 均线（可选）
        # df['ma20'] = df['close'].rolling(20).mean()
        # fplt.plot(df['time'], df['ma20'], width=1)

        # # 显示图表
        # fplt.show()


        self.start()

    def start(self):
        self.main_loop()
        #self.compute_comm_factor(2)

    def display_dashboard(self,data_dict):
        self.clear_screen() 
       # info = mt5.symbol_info(self.symbol)
       # print(info)
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print("="*100)
        print(f"伦敦金实时监控面板 [{now}]")
        print("="*100)

        self.compute_profit_risk()
        for period, df in data_dict.items():
            if df is None:
                print(f"[{period}] 数据获取失败")
                continue

            df=self.fit_transform(df)
            data_dict[period] = df

         
            # price_avg = df['avg_cur'].iloc[-1]
            # price_avg_pre = df['avg_pre'].iloc[-1]
            # price_range = df['price_range'].iloc[-1]
            # price_range_avg = df['price_range_avg'].iloc[-1]

            # std_range, atr =self.calc_price_stability(df)
            # activity_factor =self.calc_activity_factor(df)
            # price_pos=df['price_pos'].iloc[-1]


            # 输出
            # if period!="W1" and  period!="D1":
            # #if True:
            #     print(f"\n[市场监控 {period}]")

            #     self.display_line("当前区间", price_range)
            #     self.display_line("平均区间", price_range_avg)
                
            #     self.display_line("当前均价", price_avg)
            #     self.display_line("上次均价", price_avg_pre)

            #     self.display_line("价格位置", price_pos,note=self.price_position_label(price_pos))
    

            #     self.display_line("成交量活跃级别", activity_factor,note=self.activity_level(activity_factor))


            #     self.display_line("价格稳定度(Std Range)", std_range,note=self.price_stability_level_desc(std_range, atr))
            
        print(f"\n[市场监控]")

        # timeframes = {'1m': mt5.TIMEFRAME_M1, '5m': mt5.TIMEFRAME_M5, '15m': mt5.TIMEFRAME_M15}

        # merged = self.multi_window_gmaf(self.symbol, timeframes, BARS)
        # merged = self.assign_activity_level(merged)

        # latest = merged.iloc[-1]  # 获取最后一条
        # #print(f"最新K线时间: {latest.name}, GMAF_combined: {latest['GMAF_combined']:.2f}, 活跃度: {latest['activity_level']}")


        # self.display_line("活跃级别", latest['GMAF_combined'],note=latest['activity_level'])

        df_m15=data_dict['M15']

        activity_factor =self.calc_activity_factor(df_m15)
        self.display_line("活跃级别", activity_factor,note=self.activity_level(activity_factor))

        desc=self.color_value('必须确认11:30收盘价,单次波动参考:40',"cyan")
        self.display_line("均值回踩", "否",note=desc)

    

       

        time_steps=df_m15["price_range"].quantile(0.75)

        time_steps=self.fmt_num(time_steps,0)

        self.display_line("波动步长", time_steps,note="时间窗15M")


        print(f"\n[账户持仓]")
        position=self.get_position_volume_summary()
        total_volume=position["total_volume"]
        buy_volume=position["buy_volume"]
        sell_volume=position["sell_volume"]
        net_volume=position["net_volume"]

        buy_avg_price=position["buy_avg_price"]
        sell_avg_price=position["sell_avg_price"]
        long_short_ratio=position["long_short_ratio"]
#
        pos_text=f"多单:{self.fmt_num(buy_volume)},空单:{self.fmt_num(sell_volume)},目标:{self.fmt_num(TARGET_POSITION)}手,净手数:{self.fmt_num(net_volume)},浮盈比例:{self.profit_rate}%,止损价格:{self.lost_price}"

        pos_info=f"多单均价:{self.fmt_num(buy_avg_price)},空单均价:{self.fmt_num(sell_avg_price)}，多空比:{self.fmt_num(long_short_ratio)}"



        self.display_line("当前持仓", pos_text,note=pos_info)




        print(f"\n[决策参考]")
        d1=data_dict['D1']
        price_avg_1 = d1['avg_cur'].iloc[-1]
        price_avg_2 = d1['avg_pre'].iloc[-1]
        price_avg_3 = d1['avg_last'].iloc[-1]

        price_low=self.fmt_num(d1['low'].iloc[-1],0)
        price_high=self.fmt_num(d1['high'].iloc[-1],0)

        price_avg_1=self.fmt_num(price_avg_1,0)
        price_avg_2=self.fmt_num(price_avg_2,0)
        price_avg_3=self.fmt_num(price_avg_3,0)
        
        price_close = df['close'].iloc[-1]

        # prox_text=self.range_proximity(price_close,price_avg_1)

        # self.display_line("价格贴近均线预警", prox_text)

        

      
 
        w1=data_dict['W1']
        price_avg_w1 = w1['avg_cur'].iloc[-1]
        price_avg_w2 = w1['avg_pre'].iloc[-1]
        price_avg_w3 = w1['avg_last'].iloc[-1]

        price_avg_w1=self.fmt_num(price_avg_w1,0)
        price_avg_w2=self.fmt_num(price_avg_w2,0)
        price_avg_w3=self.fmt_num(price_avg_w3,0)

        offset_points=price_close-price_avg_1
        offset_points=self.fmt_num(abs(offset_points),0)

        offset_points_w1=price_close-price_avg_w1
        offset_points_w1=self.fmt_num(abs(offset_points_w1),0)

        price_range=d1['price_range'].iloc[-1]
        price_range=self.fmt_num(price_range,0)

        price_range_w1=w1['price_range'].iloc[-1]
        price_range_w1=self.fmt_num(price_range_w1,0)

        offset_points_text=f"日线偏离:{offset_points}"
        if offset_points>=22:
            offset_points_text=self.color_value(offset_points_text,"red")

        offset_points_w1_text=f"周线偏离:{offset_points_w1}"
        if offset_points_w1*2>=100:
             offset_points_w1_text=self.color_value(offset_points_w1_text,"yellow")


        self.display_line("当前最新价", price_close,note=f"{offset_points_text},{offset_points_w1_text}")

      


        estimate=self.estimate_price_range_with_remaining(d1,price_close)
        




        self.display_line("日线均值", f"{price_avg_1},{price_avg_2},{price_avg_3}",note=f"日线波动:{price_range},最低:{price_low},最高:{price_high}")

        price_low=self.fmt_num(w1['low'].iloc[-1],0)
        price_high=self.fmt_num(w1['high'].iloc[-1],0)

        self.display_line("周线均值", f"{price_avg_w1},{price_avg_w2},{price_avg_w3}",note=f"周线波动:{price_range_w1},最低:{price_low},最高:{price_high}")

        print("\n")
        

        for item in estimate["price_ranges"]:
            #print(item["level"], item["extreme_val"], item["remaining_points"], item["range"])
            est_low, est_high =item["range"]
            fixed_low, fixed_high =item["fixed"]
            est_low=self.fmt_num(est_low,0)
            est_high=self.fmt_num(est_high,0)

            fixed_low=self.fmt_num(fixed_low,0)
            fixed_high=self.fmt_num(fixed_high,0)


            profit_long=item["profit_long"]
            profit_short=item["profit_short"]
            
            level=item["level"]
            q_val =item["q_val"]
            remaining_points=item["remaining_points"]
            q_val=self.fmt_num(q_val,0)
            remaining_points=self.fmt_num(remaining_points,0)
            
      
            
            desc=f"低位:{fixed_low},高位:{fixed_high},分位点:{level},极值:{q_val},剩余:{remaining_points}"

            desc=self.color_value(desc,"cyan")
            

            #self.display_line("日线置信区间" if item["level"] == "q50" else "", f'{est_low}-{est_high}',note=desc)
            self.display_line("日线置信区间" if item["level"] == "q50" else "", f'{profit_short},{profit_long}',note=desc)
        estimate=self.estimate_price_range_with_remaining(w1,price_close)

        print("\n")
        
        for item in estimate["price_ranges"]:
            #print(item["level"], item["extreme_val"], item["remaining_points"], item["range"])
            est_low, est_high =item["range"]
            fixed_low, fixed_high =item["fixed"]
            est_low=self.fmt_num(est_low,0)
            est_high=self.fmt_num(est_high,0)


            fixed_low=self.fmt_num(fixed_low,0)
            fixed_high=self.fmt_num(fixed_high,0)

            mid_price  =item["mid_price"]
            mid_price=self.fmt_num(mid_price,0)

            
            profit_long=item["profit_long"]
            profit_short=item["profit_short"]
            


            level=item["level"]
            q_val =item["q_val"]
            remaining_points=item["remaining_points"]
            q_val=self.fmt_num(q_val,0)
            remaining_points=self.fmt_num(remaining_points,0)

            desc=f"低位:{fixed_low},高位:{fixed_high},分位点:{level},极值:{q_val},剩余:{remaining_points}"
            desc=self.color_value(desc,"cyan")

            self.display_line("周线置信区间" if item["level"] == "q50" else "", f'{profit_short},{profit_long}',note=desc)
        


        print("\n")


        now_session, now_desc = self.get_gold_session_with_range()

         

        self.display_line("运行时区", now_desc)#
        print("\n")
        desc=self.color_value('始终保持同一方向',"cyan")
        self.display_line("入场提示", f"均值回踩,日线均值偏移大于:50,周线首次突破:150，明确日线持仓时间,开盘1小时内不要操作",note=f"可配合锁仓上下操作,日波动小于20建议观望,{desc}")
        print("\n")
        self.display_line("风险提示", f"保护本金为第一原则,如需解套回踩可以作为参考，警惕日内过山车行情，减少操作可避免心理带来的情绪问题")
        print("\n")
        self.display_line("备忘录", f"美国节假日需注意机构恶意操作，如切断网络，禁用下单，本次记录时间:2025/11/28 下午13:20,感恩节")

    def compute_gmaf(self,df, atr_period=14, vol_period=10):
        df = df.copy()
        df['volume'] = df['tick_volume']
        df['range'] = df['high'] - df['low']
        df['atr'] = df['range'].rolling(atr_period).mean()
        df['return'] = df['close'].pct_change()
        df['volatility'] = df['return'].rolling(vol_period).std()
        df['body'] = abs(df['close'] - df['open'])
        df['range_penetration'] = df['body'] / (df['range'] + 1e-9)
        df['vol_change'] = df['volatility'].pct_change().fillna(0)
        
        features = ['volume','atr','range_penetration','volatility','vol_change']
        for f in features:
            df[f'z_{f}'] = (df[f]-df[f].mean())/(df[f].std()+1e-9)
        
        X = df[[f'z_{f}' for f in features]].fillna(0).values
        pca = PCA(n_components=1)
        df['gmaf'] = pca.fit_transform(X).flatten()
        df['gmaf'] = (df['gmaf']-df['gmaf'].mean())/(df['gmaf'].std()+1e-9)
        return df[['time','gmaf']]

    def multi_window_gmaf(self,symbol, timeframes, n_bars, weights=None):
        if weights is None:
            #weights = {k:1/len(timeframes) for k in timeframes.keys()}
            weights = {'1m':0.3,'5m':0.3,'15m':0.4}  # 调整权重，避免短周期主导
        
        df_dict = {}
        for name, tf in timeframes.items():
            rates = mt5.copy_rates_from_pos(symbol, tf, 0, n_bars)
            df_tf = pd.DataFrame(rates)
            df_tf['time'] = pd.to_datetime(df_tf['time'], unit='s')
            df_dict[name] =self.compute_gmaf(df_tf)
        
        merged = df_dict[list(timeframes.keys())[0]].set_index('time')
        for name, df_tf in df_dict.items():
            merged = merged.join(df_tf.set_index('time'), rsuffix=f"_{name}", how='outer')
        
        merged =  merged.ffill() 
        merged['GMAF_combined'] = sum([merged[f'gmaf_{k}']*w for k,w in weights.items()])
        return merged


    def assign_activity_level(self,df, high_q=0.75, low_q=0.25):
        high_thr = df['GMAF_combined'].quantile(high_q)
        low_thr = df['GMAF_combined'].quantile(low_q)
        def classify(factor):
            # if factor>=high_thr: return "High"
            # elif factor<=low_thr: return "Low"
            # else: return "Medium"
            """
            根据活跃因子 factor 返回等级及描述
            """

            #print(high_thr,low_thr)
            if factor>=high_thr:
                level = "高"
                desc = "市场交投活跃"
                color = "red"
            elif factor<=low_thr:
                level = "低"
                desc = "市场交投冷清"
                color = "green"
            else:
                level = "中"
                desc = "市场交投正常"
                color = "yellow"

            return self.color_value(f"{level} → {desc}", color)
            
        df['activity_level'] = df['GMAF_combined'].apply(classify)
        return df


   

    def get_gold_session_with_range(self,ts=None, band=15, with_desc=True):
        """
        返回当前交易时段信息，含：
        - 时区名称
        - 北京时间段
        - 主导市场
        - 波动特征
        - ±band 美元波动提示
        """
        if ts is None:
            ts = datetime.now()  # 默认北京时间
        
        # 转换为 UTC
        ts_utc = ts - timedelta(hours=8)
        hour = ts_utc.hour

        if 0 <= hour < 8:
            session = "Asia"
            desc = f"亚洲时段 (08:00-16:00 北京时间) — 香港/东京主导，波动较平静，参考 ±{20} 美元"
        elif 8 <= hour < 14:
            session = "Europe"
            desc = f"欧洲时段 (16:00-22:00 北京时间) — 伦敦主导，波动加大，参考 ±{30} 美元"
        elif 14 <= hour < 22:
            session = "US"
            desc = f"美洲时段 (22:00-06:00 北京时间) — 纽约主导，波动最强，参考 ±{40} 美元"
        else:
            session = "Other"
            desc = f"清淡时段 (06:00-08:00 北京时间) — 市场冷清，波动较小"

        return (session, desc) if with_desc else session




    def range_proximity(self,price, mean_price):
        """
        计算价格接近度与方向提示，颜色根据接近度变化
        """
        deviation_ratio = abs(price - mean_price) / mean_price
        proximity = (1 - deviation_ratio) * 100

        # 确定方向
        if price < mean_price:
            direction = "低于均价"
            base_color = "green"
        elif price > mean_price:
            direction = "高于均价"
            base_color = "red"
        else:
            direction = "等于均价"
            return self.color_value(f"{direction} → 接近度 100%", "yellow")

        # 根据接近度调整颜色强度（简单示例：浅中深）
        if proximity >= 90:
            color_intensity = "light"  # 浅色
        elif proximity >= 70:
            color_intensity = "medium"  # 中等色
        else:
            color_intensity = "dark"  # 深色

        # 拼接颜色名称，self.color_value 可以根据命名解析
        color_name = f"{base_color}"
        msg = f"{direction} → 接近度 {round(proximity,2)}%"
        return self.color_value(msg, color_name)

    def profit_factor(self,position_side, entry_price, target_price, volum=0, contract_size=100):
        """
        计算持仓到达指定价格的利润（仅返回 profit_usd）
        """
        if position_side.lower() == 'buy':
            profit = (target_price - entry_price)# * volume * contract_size

        else:
            profit = (entry_price - target_price)# * volume * contract_size

        
        profit=max(0, profit)    

             

        return round(profit, 0)
        
    def estimate_price_range_with_remaining(self,df, current_price, today_range=None, atr_period=14, confidence_levels=[0.5,0.75,0.85,0.95,1]):
        """
        返回结构化结果，方便循环输出
        """
        df = df.copy()
        df["range"] = df["high"] - df["low"]
        avg_range = df["range"].mean()

        df["prev_close"] = df["close"].shift(1)
        df["tr"] = df.apply(lambda row: max(
            row["high"] - row["low"],
            abs(row["high"] - row["prev_close"]) if pd.notna(row["prev_close"]) else 0,
            abs(row["low"] - row["prev_close"]) if pd.notna(row["prev_close"]) else 0
        ), axis=1)

        atr = df["tr"].tail(atr_period).mean()
        latest_close = df["close"].iloc[-1]
        price_low = latest_close - atr
        price_high = latest_close + atr



        # 今天已走波动
        if today_range is None:
            today_range = df.iloc[-1]["range"]

        last_low = df['low'].iloc[-1]
        last_high = df['high'].iloc[-1]

        current_price=self.fmt_num(current_price,0)
        # 保存结果
        price_ranges = []
        for q in confidence_levels:
            q_val = df["range"].quantile(q)
            remaining = max(0, q_val - today_range)
            label = f"q{int(q*100)}"
            low, high = current_price - remaining, current_price + remaining

            mid_price = last_low+q_val / 2

         
            fixed_low=last_high-q_val
            fixed_high=last_low+q_val

            fixed_high=self.fmt_num(fixed_high,0)
            fixed_low=self.fmt_num(fixed_low,0)

            profit_long=self.profit_factor('buy',current_price,fixed_high,TARGET_POSITION)
            profit_short=self.profit_factor('sell',current_price,fixed_low,TARGET_POSITION)

            # profit_long=abs(profit_long)
            # profit_short=abs(profit_short)
            

            price_ranges.append({
                "level": label,
                "profit_long":profit_long,
                "profit_short":profit_short,
                "q_val": q_val,
                'mid_price':mid_price,

                "remaining_points": remaining,
                "range": (low, high),
                "fixed":(fixed_low,fixed_high)
            })


        return {
            "avg_range": avg_range,
            "atr": atr,
            "range": (price_low, price_high),
            "today_range": today_range,
            "price_ranges": price_ranges
        }


    def get_position_volume_summary(self):
        """
        获取指定标的的仓位信息（按手数统计 + 均价 + 多空比）
        :return: dict
        """
        positions = mt5.positions_get(symbol=self.symbol)

        buy_volume = 0.0
        sell_volume = 0.0
        buy_weighted_price = 0.0
        sell_weighted_price = 0.0

        if positions is not None:
            for pos in positions:
                if pos.type == mt5.POSITION_TYPE_BUY:   # 多单
                    buy_volume += pos.volume
                    buy_weighted_price += pos.price_open * pos.volume

                elif pos.type == mt5.POSITION_TYPE_SELL:  # 空单
                    sell_volume += pos.volume
                    sell_weighted_price += pos.price_open * pos.volume

        total_volume = buy_volume + sell_volume
        net_volume = self.fmt_num(buy_volume) - self.fmt_num(sell_volume)  # 正数=多头占优，负数=空头占优

        # 计算均价（避免除零错误）
        buy_avg_price = buy_weighted_price / buy_volume if buy_volume > 0 else 0.0
        sell_avg_price = sell_weighted_price / sell_volume if sell_volume > 0 else 0.0

        # 多空比（>1 表示多头占优，<1 表示空头占优）
        long_short_ratio = (buy_volume / (sell_volume + 1e-9)) if sell_volume > 0 else float("inf") if buy_volume > 0 else 0.0

        return {
            "buy_volume": buy_volume,
            "sell_volume": sell_volume,
            "total_volume": total_volume,
            "net_volume": net_volume,
            "buy_avg_price": buy_avg_price,
            "sell_avg_price": sell_avg_price,
            "long_short_ratio": long_short_ratio
        }
   
    def calc_proportion(self,df, threshold=60, col="range"):
        """
        计算某列大于阈值的占比，并返回详细信息
        
        参数：
        - df: DataFrame
        - threshold: float, 阈值
        - col: str, 列名，默认 "range"
        
        返回：
        dict 包含:
        - count_above: 大于阈值的数量
        - total: 总数量
        - proportion: 占比 (0~1)
        """
        if col not in df.columns:
            raise ValueError(f"DataFrame 中没有列: {col}")
        
        count_above = (df[col] > threshold).sum()
        total = df[col].count()
        proportion = count_above / total if total > 0 else 0
        
        return {
            "count_above": count_above,
            "total": total,
            "proportion": proportion
        }  
    def price_stability_level_desc(self,std_range, atr):
        """
        根据 std_range 和 atr 返回价格稳定性等级及描述（组合情况映射）
        """
        if std_range > atr and atr > 1:  # 高std，高ATR
            level = "低"
            desc = "波动大且不稳定，市场容易出现剧烈行情"
        elif std_range < atr * 0.5 and atr < 1:  # 低std，低ATR
            level = "高"
            desc = "波动小且稳定，市场震荡或整理阶段"
        elif std_range > atr * 1.5 and atr < 1:  # 高std，低ATR
            level = "中"
            desc = "振幅变化大但平均波幅小，可能出现跳空或偶发大K线"
        elif std_range < atr and atr > 1:  # 低std，高ATR
            level = "中"
            desc = "平均波幅大但波动稳定，价格稳步上涨/下跌"
        else:  # 其他情况
            level = "中"
            desc = "市场波动中等"
        
        return f"{level} → {desc}"

    def price_position_label(self,value):
        """
        根据价格位置返回区间标签
        - 0 ~ 0.3 : 低位
        - 0.3 ~ 0.7 : 中位
        - 0.7 ~ 1.0 : 高位
        """
        if value is None:
            return self.color_value("未知", "white")

        try:
            v = float(value)
        except (ValueError, TypeError):
            return self.color_value("无效", "magenta")

        if v < 0.3:
            return self.color_value("低位", "green")
        elif v < 0.7:
            return self.color_value("中位", "yellow")
        else:
            return self.color_value("高位", "red")
         
       
    def fixed_width(self,text, width):
        real_width = wcswidth(str(text))
        pad = width - real_width
        return str(text) + " " * max(pad, 0)

    # ---------- ASCII条形图 ----------
    def ascii_bar(self,value, max_value, length=20):
        ratio = min(max(value / max_value, 0), 1)
        filled = int(ratio * length)
        return "[" + "#" * filled + "-" * (length - filled) + "]"

    def display_line(self,label, value, width=25, level="normal", max_bar=None, note=None):
        label_fixed = self.fixed_width(label, width)

        # 如果是数字，先格式化成字符串
        if isinstance(value, (int, float, np.number)):
            value_str =self.fmt_num(value)
        else:
            value_str = str(value)

        # 拼接备注说明
        if note is not None:
            value_str = f"{value_str} ({note})"

        # 彩色显示
        value_colored = self.color_text(value_str, level)

        # 条形图只在数字时显示
        if max_bar and isinstance(value, (int, float, np.number)):
            bar = self.ascii_bar(value, max_bar)
            print(f"{label_fixed}: {value_colored} {bar}")
        else:
            print(f"{label_fixed}: {value_colored}")



    def color_text(self,text, level="normal"):
        if level == "ok":
            return Fore.GREEN + str(text) + Style.RESET_ALL
        if level == "warn":
            return Fore.YELLOW + str(text) + Style.RESET_ALL
        if level == "risk":
            return Fore.RED + str(text) + Style.RESET_ALL
        return str(text)

    def color_value(self,text, color):
        """
        简单示例：给文本加上 ANSI 颜色
        color 可选：red, green, yellow, blue, cyan, magenta, white
        """
        colors = {
            "red": "\033[91m",
            "green": "\033[92m",
            "yellow": "\033[93m",
            "blue": "\033[94m",
            "cyan": "\033[96m",
            "magenta": "\033[95m",
            "white": "\033[97m",
            "reset": "\033[0m"
        }
        return f"{colors.get(color, '')}{text}{colors['reset']}"

    def calc_activity_factor(self,df):
        avg_tick_volume = df['tick_volume'].mean()
        last_tick_volume = df['tick_volume'].iloc[-1]
        factor = last_tick_volume / max(avg_tick_volume,1)
        return factor

    def activity_level(self,factor):
        """
        根据活跃因子 factor 返回等级及描述
        """
        if factor < 0.7:
            level = "低"
            desc = "成交量明显低于平均，市场交投冷清"
            color = "green"
        elif factor < 1.5:
            level = "中"
            desc = "成交量接近平均，市场交投正常"
            color = "yellow"
        else:
            level = "高"
            desc = "成交量明显高于平均，市场交投活跃"
            color = "red"

        return self.color_value(f"{level} → {desc}", color)


    def clear_screen(self):
        os.system('cls' if os.name == 'nt' else 'clear')
            

    def main_loop(self):
        try:
            while True:
                data_dict = {}
                for period_name, tf in TIMEFRAMES.items():
                    df = self.fetch_data(tf, BARS)
                    data_dict[period_name] = df
                self.display_dashboard(data_dict)
                time.sleep(REFRESH_INTERVAL)
        except KeyboardInterrupt:
            print("监控已手动停止")
        finally:
            mt5.shutdown()

    def fetch_data(self, timeframe, bars):
        rates = mt5.copy_rates_from_pos(self.symbol, timeframe, 0, bars)
        if rates is None or len(rates) == 0:
            return None
        df = pd.DataFrame(rates)
        df['time'] = pd.to_datetime(df['time'], unit='s')
        return df


    def calc_price_stability(self,df):
        df['range'] = df['high'] - df['low']
        std_range = df['range'].std()
        atr = df['range'].rolling(14, min_periods=1).mean().iloc[-1]
        return std_range, atr


    def compute_profit_risk(self):
        account=mt5.account_info()
  
        self.profit_rate=0
        self.lost_price=0

        if account.balance==0:
            return

        self.profit_rate=(account.profit/account.balance)*100
        self.profit_rate=self.fmt_num(self.profit_rate,2)

        lost_price=account.balance*self.lost
        self.lost_price=self.fmt_num(lost_price,2)
            
        
        #强制平仓
        if account.profit<0 and abs(account.profit)>=lost_price:
            #print(self.color_value("","red"))
            self.display_line("触发强平","保护本金为第一原则，请谨慎操作！！！",level="risk")
            #self.force_close_all()
        


    def compute_slope(self, df):
        """计算价格斜率"""
        def slope_func(x):
            y = x.values
            return np.polyfit(np.arange(len(y)), y, 1)[0]
        return df['close'].rolling(self.window).apply(slope_func, raw=False)

    def compute_volatility_spike(self, df):
        """计算波动率突增"""
        # ATR 计算真实波幅

        return 0

    def compute_range_ratio(self, df):
        """计算振幅比"""
        range_ = df['high'] - df['low']
        avg_range = range_.rolling(self.window).mean()
        return range_ / avg_range

    def compute_mid_point(self, df):
        """compute_mid_point"""
        range_ = df['high'] - df['low']
        return range_ / 2
    
    def compute_price_avg(self, df):
        """calc_price_avg"""
        range= df['high'] - df['low']
        return range / 2+df['low']
    
    
    def compute_range(self, df):
        range_ = df['high'] - df['low']
        return range_

    def compute_trend_strength(self, df):
        """trend_strength"""
        range_ =(df['close'] - df['open'])/ df['open']
        return range_
    def compute_pos(self, df):
        """价格位置因子接近0→低位，接近1→高位"""
        range = df['high'] - df['low']
        range_ =(df['close'] - df['low'])/range
        return range_


    def fit_transform(self, df):
        """返回包含三个因子的 DataFrame"""
        df = df.copy()
        # df['slope'] = self.compute_slope(df)
        # df['volatility_spike'] = self.compute_volatility_spike(df)
       # df['range_ratio'] = self.compute_range_ratio(df)
        #df['trend_strength'] = self.compute_trend_strength(df)
     
       
        df['price_pos'] = self.compute_pos(df)
        df['price_range'] = self.compute_range(df)
        df['price_range_avg'] = df['price_range'].rolling(window=20, min_periods=1).mean()
        df['avg_cur'] = self.compute_price_avg(df)
        df['avg_pre'] = df["avg_cur"].shift(1)
        df['avg_last'] = df["avg_cur"].shift(2)

        
        # df['mid_point'] = self.compute_mid_point(df)
      
        
        
        return df
    
    def fmt_num(self,num,digits=2):
        str_num = str(num)
        if '.' in str_num:
            return float(str_num[:str_num.index('.') + 1 + digits])
        return float(str_num)

    def force_close_all(self):
        positions=mt5.positions_get(symbol=self.symbol)
             
        if len(positions)>0:
            for position in positions:

                req = dict(
                action=mt5.TRADE_ACTION_DEAL,
                symbol=position.symbol,
                volume=position.volume,
                position=position.ticket
                )
                
                spread=mt5.symbol_info_tick(self.symbol)

                            
                if position.type==0:
                    req["type"]=mt5.ORDER_TYPE_SELL
                    req["price"]=spread.bid
                else:
                    req["type"]=mt5.ORDER_TYPE_BUY
                    req["price"]=spread.ask

                req["type_filling"]=mt5.ORDER_FILLING_IOC
              
                result =mt5.order_send(req)
        

                if result is not None and result.retcode != mt5.TRADE_RETCODE_DONE:
                    print("平仓失败，错误提示：{}=>{}".format(result.retcode,result.comment))
                else:
                    print("平仓单号：{}，持仓：{}".format(position.ticket,position.volume))
    
    def limit_send(self,signal_type, open_price):
        self.limit_remove()
        price=self.num_format(open_price,2)
        request = {
        "action": mt5.TRADE_ACTION_PENDING,
        "symbol": self.symbol,
        "volume": self.volume,
        "type": mt5.ORDER_TYPE_BUY_LIMIT,
        "price": price,
       # "type_time": mt5.ORDER_TIME_GTC, # Good till cancelled
        "type_filling": mt5.ORDER_FILLING_IOC, #市场流动性 ORDER_FILLING_IOC= 按指定价尽可能多的执行
        "comment": "Api Limit Order",
        }

        if signal_type=="short":
            request["type"]=mt5.ORDER_TYPE_SELL_LIMIT
            # request["tp"]=price-self.tp
            # request["sl"]=price+self.sl,
        
        
        result = mt5.order_send(request)
        

        if result.retcode != mt5.TRADE_RETCODE_DONE:
            print(f"挂单失败，错误代码: {result.retcode}=>{result.comment}")
        else:
            print(f"挂单成功! 订单编号: {result.order}")

    def limit_remove(self):
        orders = mt5.orders_get()  
        if orders is None:
            print(f"无法获取挂单，错误代码: {mt5.last_error()}")
        else:
            if len(orders)<=0:
                return

            print(f"当前有 {len(orders)} 个挂单")

            # 遍历所有挂单并删除
            for order in orders:
                # 构建删除订单请求
                delete_request = {
                "action": mt5.TRADE_ACTION_REMOVE, # 删除订单操作
                "order": order.ticket, # 挂单的订单编号
                }

                # 发送删除订单请求
                result = mt5.order_send(delete_request)

                # 检查删除结果
                if result.retcode != mt5.TRADE_RETCODE_DONE:
                    print(f"删除挂单 {order.ticket} 失败，错误代码: {result.retcode}=>{result.comment}")
                else:
                    print(f"挂单 {order.ticket} 删除成功")



    def load_config(self):

        app_path=""
        if getattr(sys, 'frozen', False):
            app_path =os.path.abspath(os.path.dirname(sys.executable))
    
        else:
            app_path = os.path.dirname(os.path.abspath(__file__))
        
        config_path = os.path.join(app_path, 'config.json')

        with open(config_path, "r") as config_file:
            config = json.load(config_file)
            return config



if __name__ == "__main__":
 
    x=XFactorLab()
    x.initialize()
